﻿/*******************************************************************************
*Author         :  VN
*Description    :  网络通信模块管理类，管理通道创建、管理收发包；网络状态管理
*Other          :  none
*Modify Record  :  2016/3/1
*******************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using ProtoBuf;
using cs;

namespace Network
{
    public class NetChnl : MonoBehaviour
    {
        #region 内部结构体
        enum EChnlState
        {
            csInit = -1,             // 初始状态
            csStarting,              // 正在连接服务器
            csStarted,               // 正在连接成功
            csVerifyST,              // 正在验证ST中
            csConnected,             // 连接成功(验证ST成功)
            csWaitForUserConnect,    // 等待用户重连
            csClosed,                // 通道断开
        };
        #endregion;

        #region 成员变量
        static  uint  m_ChnlSeqID = 0;

        uint m_ChnlID = 0;
        private uint  m_uSeqId = 0;             // seqID
        private PackCacheQueue m_PackCache    = null;   // 发送数据包缓存
        private PackCacheQueue m_PeddingCache = null;   // 暂时缓存的包
        private Broadcast m_BroadcastHelper   = null;   // 广播处理
        private PackCodec m_PackCodec         = null;   // 组包对象
        private CmdPack m_CmdPack             = null;   //发消息前的包拼装
        private TCPChnl m_TCPChnl             = null;   // 网络通道

        public string m_strIP;
        public int    m_nPort;
        private int m_nRetryTimes  = 0;
        private EChnlState m_EChnlState  = EChnlState.csInit;   // 通道状态
        const int   MAX_AUTORETRY_TIMES  = 3;
        const float TIMEOUT_DEFAULT      = 7.8f;

        private MsgInfo m_reloginMsg = null;

        INetAlive m_NetAlive = null;

        private DelegateOnChnlConnect m_OnChnlConnect = null;   // 通道连接回调
        public DelegateOnChnlConnect OnChnlConnect
        {
            get { return m_OnChnlConnect; }
            set { m_OnChnlConnect = value; }
        }

        private DelegateOnChnlClose m_OnChnlClose = null;      // 网络通道断开
        public DelegateOnChnlClose OnChnlClose
        {
            get { return m_OnChnlClose; }
            set { m_OnChnlClose = value; }
        }
        #endregion;

        #region Init
        public NetChnl()
        {
            m_ChnlID = m_ChnlSeqID++;
            Init();
        }

        public static NetChnl CreateChnl()
        {
            NetChnl instance = (new GameObject("NetChnl")).AddComponent<NetChnl>();

            GameObject.DontDestroyOnLoad(instance.gameObject);
            
            return instance;
        }

        public void Destroy()
        {
            if (gameObject != null)
            {
                GameObject.Destroy(gameObject);
            }
        }

        private void Init()
        {
            m_TCPChnl  = new TCPChnl();
            m_PackCache    = new PackCacheQueue();       // 发送数据包缓存
            m_PeddingCache = new PackCacheQueue();    // 暂时缓存的包
            m_BroadcastHelper   = new Broadcast();  // 广播处理
            m_PackCodec         = new PackCodec(); // 组包对象
            m_nRetryTimes     = 0;
            m_CmdPack         = new CmdPack();      //发消息前的包拼装
            m_EChnlState = EChnlState.csInit;   // 通道状态
            m_NetAlive = null;
            
            RegisterEvent();
        }

        private void UnInit()
        {
            UnRegisterEvent();
            Disconnect();

            MsgInfoPool.Clear();
            m_EChnlState = EChnlState.csInit;   // 通道状态
            m_TCPChnl = null;
            m_PackCache = null;       // 发送数据包缓存
            m_PeddingCache = null;    // 暂时缓存的包
            m_BroadcastHelper = null;  // 广播处理
            m_PackCodec = null; // 组包对象
            m_nRetryTimes = 0;
            m_CmdPack = null;      //发消息前的包拼装
            m_NetAlive = null;
        }

        public void OnDestroy()
        {
            UnInit();
        }

        private void RegisterEvent()
        {
            if (m_TCPChnl != null)
            {
                m_TCPChnl.OnChnlClose -= OnClose;
                m_TCPChnl.OnChnlClose += OnClose;
                m_TCPChnl.OnChnlConnect -= OnConnect;
                m_TCPChnl.OnChnlConnect += OnConnect;
            }
        }

        private void UnRegisterEvent()
        {
            if (m_TCPChnl != null)
            {
                m_TCPChnl.OnChnlClose -= OnClose;
                m_TCPChnl.OnChnlConnect -= OnConnect;
            }

            if (m_NetAlive != null)
            {
                m_NetAlive.DelListener(OnClose);
            }
        }
        #endregion;

        #region 对外接口，参数配置
        // 特殊接口，正常不使用
        public void SetBrdcastHelper(Broadcast brdcastHelper)
        {
            m_BroadcastHelper = null;
            m_BroadcastHelper = brdcastHelper;
        }

        public void SetSecretKey(byte[] key)
        {
            m_PackCodec.SetSecretKey(key);
        }

        public void SetCompressSize(uint compressSize)
        {
            m_PackCodec.SetCompressSize(compressSize);
        }

        public void EnabledEncrypt(bool b)
        {
            if (m_PackCodec != null)
            {
                m_PackCodec.EnabledEncrypt(b);
            }
        }

        // 自动重连等逻辑，设置后；需要依赖于此包成功后，才能发送其他包
        public void SetLoginReq(uint cmdID, byte[] buf)
        {
            if (m_reloginMsg != null)
            {
                MsgInfoPool.Delete(m_reloginMsg);
                m_reloginMsg = null;
            }

/*            CSAccountLoginReq pkg = (CSAccountLoginReq)PackCodec.Deserialize<CSAccountLoginReq>(buf);*/
            m_reloginMsg = ComposeMsgInfo(cmdID, buf, OnLoginCallback, 3, TIMEOUT_DEFAULT , true);
        }

        public void SetNetAlive(INetAlive alive)
        {
            if (m_NetAlive != null)
            {
                m_NetAlive.DelListener(OnClose);
                m_NetAlive = null;
            }

            if (alive != null)
            {
                m_NetAlive = alive;
                m_NetAlive.DelListener(OnClose);
            }
        }

        void SendReogin()
        {
            SetSecretKey(null);
            SetState(EChnlState.csVerifyST);

/*            CSAccountLoginReq pkg = (CSAccountLoginReq)PackCodec.Deserialize<CSAccountLoginReq>(m_reloginMsg.Msg);*/

            m_reloginMsg.uSeqId = NextSeqID();
            m_reloginMsg.Send(m_CmdPack, m_PackCodec, m_TCPChnl);
        }

        void OnLoginCallback(uint cmdID, CMsgResult result, byte[] RecvMsg, byte[] SendMsg)
        {
            if (result == CMsgResult.NoError)
            {
                SetState(EChnlState.csConnected);
                if (m_NetAlive != null)
                {
                    m_NetAlive.StartHello();
                }
                OnChnlConnect(true , (int)CMsgResult.NoError, RecvMsg);
            }
            else
            {
                SetState(EChnlState.csWaitForUserConnect);
                if (OnChnlClose != null)
                {
                    OnChnlClose((int)CMsgResult.NetworkNoRes);
                }
            }
        }

        void SetState(EChnlState state)
        {
            m_EChnlState = state;
            Log("SetState state" + state.ToString());
        }

        void Log(string str)
        {
/*            LuaInterface.Debugger.Log(str);*/
        }

        void SendPedding()
        {
            if (!m_PeddingCache.IsEmpty() && m_EChnlState == EChnlState.csConnected)
            {
                for (MsgInfo msginfo = m_PeddingCache.Pop(); msginfo != null; msginfo = m_PeddingCache.Pop())
                {
                    msginfo.Send(m_CmdPack, m_PackCodec, m_TCPChnl);

                    if (msginfo.NeedRes)
                    {
                        this.m_PackCache.Push_Back(msginfo);
                    }
                    else
                    {
                        MsgInfoPool.Delete(msginfo);
                    }
                }
            }
        }

        bool ProcMsgTimeout(MsgInfo msginfo)
        {
            if (msginfo != null && msginfo.IsTimeOut(Time.realtimeSinceStartup))
            {
                msginfo.HandleTimeout(CMsgResult.MsgTimeOut);
                return true;
            }
            return false;
        }

        void HandleTimeout()
        {
            List<MsgInfo> msgPackList = m_PackCache.GetAll();
            for (int i = msgPackList.Count - 1; i >= 0; i--)
            {
                MsgInfo msginfo = msgPackList[i];
                if (ProcMsgTimeout(msginfo))
                {
                    msgPackList.Remove(msginfo);
                    MsgInfoPool.Delete(msginfo);
                }
            }

            List<MsgInfo> msgPeddingList = m_PeddingCache.GetAll();
            for (int i = msgPeddingList.Count - 1; i >= 0; i--)
            {
                MsgInfo msginfo = msgPeddingList[i];
                if (ProcMsgTimeout(msginfo))
                {
                    msgPeddingList.Remove(msginfo);
                    MsgInfoPool.Delete(msginfo);
                }
            }
        }

        public void Connect(string srv, int port)
        {
            if (m_strIP == srv && m_nPort == port && m_TCPChnl.IsConnected())
            {
                return;
            }
            else if (m_TCPChnl.IsConnected())
            {
                Disconnect();
            }

            m_nRetryTimes = 0;
            m_strIP = srv;
            m_nPort = port;
            SetState(EChnlState.csClosed);
        }

        public void Reconnect()
        {
            if (m_EChnlState != EChnlState.csWaitForUserConnect)
            {
                return;
            }

            if (m_TCPChnl.IsConnected())
            {
                Disconnect();
            }
            SetState(EChnlState.csClosed);
            m_nRetryTimes = 0;
        }

        // 注册广播监听
        public void AddBroadcastEventHander(uint cmdID, CMsgDelegate callback)
        {
            if (m_BroadcastHelper == null)
            {
                m_BroadcastHelper = new Broadcast();
            }
            m_BroadcastHelper.AddBroadcastEventHander(cmdID, callback);
        }

        // 移除广播监听
        public void RemoveBroadcastEventHander(uint cmdID, CMsgDelegate callback)
        {
            if (m_BroadcastHelper != null)
            {
                m_BroadcastHelper.RemoveBroadcastEventHander(cmdID, callback);
            }
        }
    
        MsgInfo ComposeMsgInfo(uint cmdID, byte[] buf, CMsgDelegate callback, int nRetryTimes, float uTimeout,bool bNeedRes, uint seqID = 0)
        {
            uint seq_id = seqID;
            if (seq_id == 0)
            {
                seq_id = NextSeqID();
            }
            return MsgInfoPool.New(seq_id, cmdID, buf, callback, nRetryTimes, uTimeout, bNeedRes);
        }

        // 断开连接
        public void Disconnect()
        {
            CallbackAllTimeout(CMsgResult.MsgChnlClose);            
            if (m_NetAlive != null)
            {
                m_NetAlive.StopHello();
            }

            if (m_TCPChnl != null)
            {
                m_TCPChnl.Disconnect();
            }
            m_EChnlState = EChnlState.csInit;
            m_nRetryTimes = 0;
            m_PackCache.ClearAll();
            m_PeddingCache.ClearAll();
        }

        public void ResendMsg(uint cmdID, uint seqID, byte[] buf, CMsgDelegate callback, int retrytime = 0, float timeout = TIMEOUT_DEFAULT)
        {
            if (m_EChnlState == EChnlState.csWaitForUserConnect)
            {
                Reconnect();
            }

            MsgInfo info = ComposeMsgInfo(cmdID, buf, callback, retrytime, timeout, true, seqID);
            m_PeddingCache.Push_Back(info);
        }


        public uint SendMsg(uint cmdID, byte[] buf, CMsgDelegate callback, int retrytime = 0, float timeout = TIMEOUT_DEFAULT)
        {
            return SendData(cmdID, buf, callback, retrytime, timeout, true);
        }

        // 发包不回包
        public void SendMsgNoRes(uint cmdID, byte[] buf)
        {
            SendData(cmdID, buf, null, 0, 0, false);
        }

        private uint SendData(uint cmdID, byte[] buf, CMsgDelegate callback, int retrytime = 0, float timeout = TIMEOUT_DEFAULT, bool bNeedRes = true)
        {
            MsgInfo info = ComposeMsgInfo(cmdID, buf, callback, retrytime, timeout, bNeedRes);
            m_PeddingCache.Push_Back(info);
            return info.uSeqId;
        }

        public uint NextSeqID()
        {
            m_uSeqId++;
            return m_uSeqId;
        }

        public uint CurSeqID()
        {
            return m_uSeqId;
        }

        bool HasIP()
        {
            return m_strIP.Length > 0 && m_nPort != 0;
        }

        void UpdateAllState()
        {
            switch (m_EChnlState)
            {
                case EChnlState.csInit:
                    // 啥都不用太做
                    break;

                case EChnlState.csStarting:
                    break;

                case EChnlState.csStarted:
                    if (m_reloginMsg != null)
                    {
                        SendReogin();
                    }
                    else
                    {
                        SetState(EChnlState.csConnected);
                        if (OnChnlConnect != null)
                        {
                            OnChnlConnect(true, (int)CMsgResult.NoError , null);
                        }
                    }
                    break;

                case EChnlState.csVerifyST:
                    if (m_reloginMsg.IsTimeOut(Time.realtimeSinceStartup))
                    {
                        m_TCPChnl.Disconnect();
                        OnChnlClose((int)CMsgResult.InternalError);

                        SetState(EChnlState.csWaitForUserConnect);
                    }
                    break;

                case EChnlState.csClosed:
                    if (m_nRetryTimes < MAX_AUTORETRY_TIMES)
                    {
                        m_nRetryTimes++;
                        m_TCPChnl.Connect(m_strIP, m_nPort);
                        SetState(EChnlState.csStarting);
                    }
                    else 
                    {
                        SetState(EChnlState.csWaitForUserConnect);
                        if (OnChnlClose != null)
                        {
                            OnChnlClose((int)CMsgResult.InternalError);
                        }
                    }
                    break;

                case EChnlState.csConnected:
                    SendPedding();
                    break;

                case EChnlState.csWaitForUserConnect:
                    // 啥都不用做,等待用户手动重连
                    break;

                default:
                    break;
            }
        }

        // 处理收包
        private void HandleRecvData()
        {
            Queue recvQuery = m_TCPChnl.GetRecvMsgQueue();
            int nCount = recvQuery.Count;
            for (int i = 0; i < nCount; i++)
            {
                byte[] pData = (byte[])recvQuery.Dequeue();
                CSPKG pkg = m_PackCodec.UnPackData(pData);      
                m_CmdPack.cmdID = pkg.Head.CmdID;
                m_CmdPack.seqNO = pkg.Head.MsgSeqID;
                m_CmdPack.buf = pkg.Body;
                if (m_CmdPack.seqNO != 0)
                {
                    MsgInfo info = m_PackCache.Find(m_CmdPack.seqNO);
                    if (info != null)
                    {
                        m_PackCache.Remove(info);
                        info.HandleCallBack(m_CmdPack.buf);
                        MsgInfoPool.Delete(info);
                    }
                    else if (m_reloginMsg != null && m_CmdPack.seqNO == m_reloginMsg.uSeqId)
                    {
                        m_reloginMsg.HandleCallBack(m_CmdPack.buf);
                    }
                }
                else
                {
                    m_BroadcastHelper.DispatchMsg(m_CmdPack);
                }
            }
        }

        private void OnClose(int errcode)
        {
            if (m_EChnlState != EChnlState.csInit)  //重置状态
            {
                SetState(EChnlState.csClosed);
            }

            if (m_NetAlive != null)
            {
                m_NetAlive.StopHello();
            }
        }

        private void OnConnect(bool bConnect, int errcode , byte[] recvData)
        {
            if (bConnect)
            {
                SetState(EChnlState.csStarted);
            }
            else
            {
                SetState(EChnlState.csClosed);
            }
        }

        // 定时处理收发包
        public void Update()
        {
            if (IsNetworkDisabled())   //单机模式，等网络不可用状态
            {
                CallbackAllTimeout(CMsgResult.NetworkError);
                return;
            }

            // 收包
            HandleRecvData();
            
            // 超时处理
            HandleTimeout();

            // 发包
            UpdateAllState();

            m_TCPChnl.Update();
        }

        // 处理所有包超时
        private void CallbackAllTimeout(CMsgResult res)
        {
            for (MsgInfo msginfo = m_PackCache.Pop(); msginfo != null; msginfo = m_PackCache.Pop())
            {
                msginfo.HandleTimeout(res);
                MsgInfoPool.Delete(msginfo);
            }
            m_PackCache.ClearAll();

            for (MsgInfo msginfo = m_PeddingCache.Pop(); msginfo != null; msginfo = m_PeddingCache.Pop())
            {
                msginfo.HandleTimeout(res);
                MsgInfoPool.Delete(msginfo);
            }
            m_PeddingCache.ClearAll();
        }

        private bool IsNetworkDisabled()
        {
            if (m_TCPChnl == null || RunMode.Instance.SingleMode
                )
            {
                return true;
            }
            return false;
        }

        //获取服务器当前的系统时间
        public uint GetCurSrvSysTime()
        {
            return m_PackCodec.srvTimeStamp;
        }

        #endregion
    }
} //end namespace